/*
	Copyright (c) 2013 MeherTJ G. All rights reserved.
	License: LGPL for personnal study or free software.
	*/

#pragma once

#include <cmath>
#include <functional>
#include <iostream>

#include "zen_point.h"

namespace Zen {
	/// pod 
	struct Vec2{
		float x, y;
		
		float & operator[](size_t i)
		{
			return (&x)[i];
		}
		float const & operator[](size_t i) const
		{
			return (&x)[i];
		}
	};
	struct Vec3 {
		float x, y, z;
		
		float & operator[](size_t i)
		{
			return (&x)[i];
		}
		float const & operator[](size_t i) const
		{
			return (&x)[i];
		}
	};
	struct Vec4 {
		float x, y, z, w;
		
		float & operator[](size_t i)
		{
			return (&x)[i];
		}
		float const & operator[](size_t i) const
		{
			return (&x)[i];
		}
	};

	typedef const Vec2 cVec2;
	typedef const Vec3 cVec3;
	typedef const Vec4 cVec4;
}

namespace Zen {
	inline Point2 Point2Make(cVec2 & v)
	{
		return { v.x, v.y };
	}
	inline Size2 Size2Make(cVec2 & v)
	{
		return { v.x, v.y };
	}
}

inline Zen::Vec2 operator-(Zen::cVec2 & v);
inline Zen::Vec2 operator+(Zen::cVec2 & vL, Zen::cVec2 & vR);
inline Zen::Vec2 operator-(Zen::cVec2 & vL, Zen::cVec2 & vR);
inline Zen::Vec2 operator*(Zen::cVec2 & vL, Zen::cVec2 & vR);
inline Zen::Vec2 operator/(Zen::cVec2 & vL, Zen::cVec2 & vR);
inline Zen::Vec2 operator+(Zen::cVec2 & v, float value);
inline Zen::Vec2 operator-(Zen::cVec2 & v, float value);
inline Zen::Vec2 operator*(Zen::cVec2 & v, float value);
inline Zen::Vec2 operator/(Zen::cVec2 & v, float value);
inline bool operator==(Zen::cVec2 & vL, Zen::cVec2 & vR);
inline bool operator==(Zen::cVec2 & v, float value);

inline Zen::Vec3 operator-(Zen::cVec3 & v);
inline Zen::Vec3 operator+(Zen::cVec3 & vL, Zen::cVec3 & vR);
inline Zen::Vec3 operator-(Zen::cVec3 & vL, Zen::cVec3 & vR);
inline Zen::Vec3 operator*(Zen::cVec3 & vL, Zen::cVec3 & vR);
inline Zen::Vec3 operator/(Zen::cVec3 & vL, Zen::cVec3 & vR);
inline Zen::Vec3 operator+(Zen::cVec3 & v, float value);
inline Zen::Vec3 operator-(Zen::cVec3 & v, float value);
inline Zen::Vec3 operator*(Zen::cVec3 & v, float value);
inline Zen::Vec3 operator/(Zen::cVec3 & v, float value);
inline bool operator==(Zen::cVec3 & vL, Zen::cVec3 & vR);
inline bool operator==(Zen::cVec3 & v, float value);

inline Zen::Vec4 operator-(Zen::cVec4 & v);
inline Zen::Vec4 operator+(Zen::cVec4 & vL, Zen::cVec4 & vR);
inline Zen::Vec4 operator-(Zen::cVec4 & vL, Zen::cVec4 & vR);
inline Zen::Vec4 operator*(Zen::cVec4 & vL, Zen::cVec4 & vR);
inline Zen::Vec4 operator/(Zen::cVec4 & vL, Zen::cVec4 & vR);
inline Zen::Vec4 operator+(Zen::cVec4 & v, float value);
inline Zen::Vec4 operator-(Zen::cVec4 & v, float value);
inline Zen::Vec4 operator*(Zen::cVec4 & v, float value);
inline Zen::Vec4 operator/(Zen::cVec4 & v, float value);
inline bool operator==(Zen::cVec4 & vL, Zen::cVec4 & vR);
inline bool operator==(Zen::cVec4 & v, float value);

namespace Zen {
	
	inline Vec2 Vec2Make(float x, float y);
	inline Vec2 Vec2Make(float * values /* [2] */);
	inline Vec2 Vec2Make(Point2 p);
	inline Vec2 Vec2Normalize(cVec2 & v);
	inline float Vec2DotProduct(cVec2 & vL, cVec2 & vR);
	inline float Vec2Length(cVec2 & v);
	inline float Vec2Distance(cVec2 & vStart, cVec2 & vEnd);
	inline float Vec2Length2(cVec2 & v);
	inline float Vec2Distance2(cVec2 & vStart, cVec2 & vEnd);
	inline Vec2 Vec2Lerp(cVec2 & vStart, cVec2 & vEnd, float t);
	inline Vec2 Vec2Project(cVec2 & v, Vec2 p);
	
	inline Vec3 Vec3Make(float x, float y, float z);
	inline Vec3 Vec3Make(float *values /*[3]*/);
	inline Vec3 Vec3Make(Point3 const & p);
	inline Vec3 Vec3Normalize(cVec3 & v);
	inline float Vec3DotProduct(cVec3 & vL, cVec3 & vR);
	inline float Vec3Length(cVec3 & v);
	inline float Vec3Distance(cVec3 & vStart, cVec3 & vEnd);
	inline float Vec3Length2(cVec3 & v);
	inline float Vec3Distance2(cVec3 & vStart, cVec3 & vEnd);
	inline Vec3 Vec3Lerp(cVec3 & vStart, cVec3 & vEnd, float t);
	inline Vec3 Vec3CrossProduct(cVec3 & vL, cVec3 & vR);
	inline Vec3 Vec3Project(cVec3 & v, Vec3 p);
	
	inline Vec4 Vec4Make(float x, float y, float z, float w);
	inline Vec4 Vec4Make(float * values /*[4]*/);
	inline Vec4 Vec4Make(cVec3 & v, float w);
	inline Vec4 Vec4Normalize(cVec4 & v);
	inline float Vec4DotProduct(cVec4 & vL, cVec4 & vR);
	inline float Vec4Length(cVec4 & v);
	inline float Vec4Distance(cVec4 & vStart, cVec4 & vEnd);
	inline float Vec4Length2(cVec4 & v);
	inline float Vec4Distance2(cVec4 & vStart, cVec4 & vEnd);
	inline Vec4 Vec4Lerp(cVec4 & vStart, cVec4 & vEnd, float t);
	/*
		Performs a 3D cross product. The last component of the resulting cross product will be zeroed out.
		*/
	inline Vec4 Vec4CrossProduct(cVec4 & vL, cVec4 & vR);
	inline Vec4 Vec4Project(cVec4 & v, Vec4 p);

}

namespace Zen {
	inline Vec2 Vec2Make(float x, float y)
	{
		return { x, y };
	}
	
	inline Vec2 Vec2Make(float * values /* [2] */)
	{
		return { values[0], values[1] };
	}
	inline Vec2 Vec2Make(Point2 p) {
		return { p.x, p.y };
	}

	inline Vec2 Vec2Make(Size2 s) {
		return { s.w, s.h };
	}
	
	inline Vec2 Vec2Normalize(cVec2 & v)
	{
		float scale = 1.0f / Vec2Length(v);
		return v * scale;
	}
	
	inline float Vec2DotProduct(cVec2 & vL, cVec2 & vR)
	{
		return vL[0] * vR[0] + vL[1] * vR[1];
	}
	
	inline float Vec2Length(cVec2 & v)
	{
		return sqrt(v[0] * v[0] + v[1] * v[1]);
	}
	
	inline float Vec2Distance(cVec2 & vStart, cVec2 & vEnd)
	{
		return Vec2Length(vEnd - vStart);
	}
	inline float Vec2Length2(cVec2 & v)
	{
		return v[0] * v[0] + v[1] * v[1];
	}
	
	inline float Vec2Distance2(cVec2 & vStart, cVec2 & vEnd)
	{
		return Vec2Length2(vEnd - vStart);
	}
	
	inline Vec2 Vec2Lerp(cVec2 & vStart, cVec2 & vEnd, float t)
	{
		return { vStart[0] + ((vEnd[0] - vStart[0]) * t),
			vStart[1] + ((vEnd[1] - vStart[1]) * t) };
	}
	
	inline Vec2 Vec2Project(cVec2 & v, Vec2 p)
	{
		float scale = Vec2DotProduct(p, v) / Vec2DotProduct(p, p);
		return p * scale;
	}
	
#pragma mark Implementations
	
	inline Vec3 Vec3Make(float x, float y, float z)
	{
		return { x, y, z };
	}
	
	inline Vec3 Vec3Make(float *values /*[3]*/)
	{
		return { values[0], values[1], values[2] };
	}
	inline Vec3 Vec3Make(Point3 const & p) {
		return { p.x, p.y, p.z };
	}

	inline Vec3 Vec3Make(cVec4 & v4) {
		return { v4.x, v4.y, v4.z };
	}
	inline Vec3 Vec3Normalize(cVec3 & v)
	{
		float scale = 1.0f / Vec3Length(v);
		return { v[0] * scale, v[1] * scale, v[2] * scale };
	}
	
	inline float Vec3DotProduct(cVec3 & vL, cVec3 & vR)
	{
		return vL[0] * vR[0] + vL[1] * vR[1] + vL[2] * vR[2];
	}
	
	inline float Vec3Length(cVec3 & v)
	{
		return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	}
	
	inline float Vec3Distance(cVec3 & vStart, cVec3 & vEnd)
	{
		return Vec3Length(vEnd - vStart);
	}
	inline float Vec3Length2(cVec3 & v)
	{
		return (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
	}
	
	inline float Vec3Distance2(cVec3 & vStart, cVec3 & vEnd)
	{
		return Vec3Length2(vEnd - vStart);
	}
	
	inline Vec3 Vec3Lerp(cVec3 & vStart, cVec3 & vEnd, float t)
	{
		return {
			vStart[0] + ((vEnd[0] - vStart[0]) * t),
			vStart[1] + ((vEnd[1] - vStart[1]) * t),
			vStart[2] + ((vEnd[2] - vStart[2]) * t)
		};
	}
	
	inline Vec3 Vec3CrossProduct(cVec3 & vL, cVec3 & vR)
	{
return {
			vL[1] * vR[2] - vL[2] * vR[1],
			vL[2] * vR[0] - vL[0] * vR[2],
			vL[0] * vR[1] - vL[1] * vR[0]
		};
	}
	
	inline Vec3 Vec3Project(cVec3 & v, Vec3 p)
	{
		float scale = Vec3DotProduct(p, v) / Vec3DotProduct(p, p);
		return p * scale;
	}
	
	inline Vec4 Vec4Make(float x, float y, float z, float w)
	{
		cVec4 & v = { x, y, z, w };
		return v;
	}
	
	inline Vec4 Vec4Make(float * values /*[4]*/)
	{
		cVec4 & v = { values[0], values[1], values[2], values[3] };
		return v;
	}
	
	inline Vec4 Vec4Make(cVec3 & v, float w)
	{
		return { v.x, v.y, v.z, w };
	}
	
	inline Vec4 Vec4Normalize(cVec4 & v)
	{
		float scale = 1.0f / Vec4Length(v);
		return v * scale;
	}
	
	inline float Vec4DotProduct(cVec4 & vL, cVec4 & vR)
	{
		return vL[0] * vR[0] + vL[1] * vR[1] + vL[2] * vR[2] + vL[3] * vR[3];
	}
	
	inline float Vec4Length(cVec4 & v)
	{
		return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
	}
	
	inline float Vec4Distance(cVec4 & vStart, cVec4 & vEnd)
	{
		return Vec4Length(operator-(vEnd, vStart));
	}
	inline float Vec4Length2(cVec4 & v)
	{
		return (v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
	}
	
	inline float Vec4Distance2(cVec4 & vStart, cVec4 & vEnd)
	{
		return Vec4Length2(vEnd - vStart);
	}
	
	inline Vec4 Vec4Lerp(cVec4 & vStart, cVec4 & vEnd, float t)
	{
		return { vStart[0] + ((vEnd[0] - vStart[0]) * t),
			vStart[1] + ((vEnd[1] - vStart[1]) * t),
			vStart[2] + ((vEnd[2] - vStart[2]) * t),
			vStart[3] + ((vEnd[3] - vStart[3]) * t) };
	}
	
	inline Vec4 Vec4CrossProduct(cVec4 & vL, cVec4 & vR)
	{
return { vL[1] * vR[2] - vL[2] * vR[1],
			vL[2] * vR[0] - vL[0] * vR[2],
			vL[0] * vR[1] - vL[1] * vR[0],
			0.0f };
	}
	
	inline Vec4 Vec4Project(cVec4 & v, Vec4 p)
	{
		float scale = Vec4DotProduct(p, v) / Vec4DotProduct(p, p);
		return operator*(p, scale);
	}
}

/* Zen::Vec2 operator **/

inline Zen::Vec2 operator-(Zen::cVec2 & v)
{
	return { -v[0] , -v[1] };
}

inline Zen::Vec2 operator+(Zen::cVec2 & vL, Zen::cVec2 & vR)
{
	return { vL[0] + vR[0], vL[1] + vR[1] };
}

inline Zen::Vec2 operator-(Zen::cVec2 & vL, Zen::cVec2 & vR)
{
	return { vL[0] - vR[0], vL[1] - vR[1] };
}

inline Zen::Vec2 operator*(Zen::cVec2 & vL, Zen::cVec2 & vR)
{
	return { vL[0] * vR[0], vL[1] * vR[1] };
}

inline Zen::Vec2 operator/(Zen::cVec2 & vL, Zen::cVec2 & vR)
{
	return { vL[0] / vR[0], vL[1] / vR[1] };
}

inline Zen::Vec2 operator+(Zen::cVec2 & v, float value)
{
	return { v[0] + value, v[1] + value };
}

inline Zen::Vec2 operator-(Zen::cVec2 & v, float value)
{
	return { v[0] - value, v[1] - value };
}

inline Zen::Vec2 operator*(Zen::cVec2 & v, float value)
{
	return { v[0] * value, v[1] * value };
}

inline Zen::Vec2 operator/(Zen::cVec2 & v, float value)
{
	return { v[0] / value, v[1] / value };
}

inline bool operator==(Zen::cVec2 & vL, Zen::cVec2 & vR)
{
	return (vL[0] == vR[0] && vL[1] == vR[1]);
}

inline bool operator==(Zen::cVec2 & v, float value)
{
	return (v[0] == value && v[1] == value);
}

/* Zen::Vec3 operator **/
inline Zen::Vec3 operator-(Zen::cVec3 & v)
{
	return { -v[0], -v[1], -v[2] };
}

inline Zen::Vec3 operator+(Zen::cVec3 & vL, Zen::cVec3 & vR)
{
	return { vL[0] + vR[0], vL[1] + vR[1], vL[2] + vR[2] };
}

inline Zen::Vec3 operator-(Zen::cVec3 & vL, Zen::cVec3 & vR)
{
	return { vL[0] - vR[0], vL[1] - vR[1], vL[2] - vR[2] };
}

inline Zen::Vec3 operator*(Zen::cVec3 & vL, Zen::cVec3 & vR)
{
	return { vL[0] * vR[0], vL[1] * vR[1], vL[2] * vR[2] };
}

inline Zen::Vec3 operator/(Zen::cVec3 & vL, Zen::cVec3 & vR)
{
	return { vL[0] / vR[0], vL[1] / vR[1], vL[2] / vR[2] };
}

inline Zen::Vec3 operator+(Zen::cVec3 & v, float value)
{
	return { v[0] + value, v[1] + value, v[2] + value };
}

inline Zen::Vec3 operator-(Zen::cVec3 & v, float value)
{
	return { v[0] - value, v[1] - value, v[2] - value };
}

inline Zen::Vec3 operator*(Zen::cVec3 & v, float value)
{
	return { v[0] * value, v[1] * value, v[2] * value };
}

inline Zen::Vec3 operator/(Zen::cVec3 & v, float value)
{
	return { v[0] / value, v[1] / value, v[2] / value };
}


inline bool operator==(Zen::cVec3 & vL, Zen::cVec3 & vR)
{
	return (vL[0] == vR[0] && vL[1] == vR[1] && vL[2] == vR[2]);
}

inline bool operator==(Zen::cVec3 & v, float value)
{
	return (v[0] == value && v[1] == value && v[2] == value);
}

inline Zen::Vec4 operator-(Zen::cVec4 & v)
{
	return { -v[0], -v[1], -v[2], -v[3] };
}

inline Zen::Vec4 operator+(Zen::cVec4 & vL, Zen::cVec4 & vR)
{
	return { vL[0] + vR[0], vL[1] + vR[1], vL[2] + vR[2], vL[3] + vR[3] };
}

inline Zen::Vec4 operator-(Zen::cVec4 & vL, Zen::cVec4 & vR)
{
	return { vL[0] - vR[0], vL[1] - vR[1], vL[2] - vR[2], vL[3] - vR[3] };
}

inline Zen::Vec4 operator*(Zen::cVec4 & vL, Zen::cVec4 & vR)
{
	return { vL[0] * vR[0], vL[1] * vR[1], vL[2] * vR[2], vL[3] * vR[3] };
}

inline Zen::Vec4 operator/(Zen::cVec4 & vL, Zen::cVec4 & vR)
{
	return { vL[0] / vR[0], vL[1] / vR[1], vL[2] / vR[2], vL[3] / vR[3] };
}

inline Zen::Vec4 operator+(Zen::cVec4 & v, float value)
{
	return { v[0] + value, v[1] + value, v[2] + value, v[3] + value };
}

inline Zen::Vec4 operator-(Zen::cVec4 & v, float value)
{
	return { v[0] - value, v[1] - value, v[2] - value, v[3] - value };
}

inline Zen::Vec4 operator*(Zen::cVec4 & v, float value)
{
	return { v[0] * value, v[1] * value, v[2] * value, v[3] * value };
}

inline Zen::Vec4 operator/(Zen::cVec4 & v, float value)
{
	return { v[0] / value, v[1] / value, v[2] / value, v[3] / value };
}

inline bool operator==(Zen::cVec4 & vL, Zen::cVec4 & vR)
{
	return (vL[0] == vR[0] &&
									vL[1] == vR[1] &&
									vL[2] == vR[2] &&
									vL[3] == vR[3]);
}

inline bool operator==(Zen::cVec4 & v, float value)
{
	return (v[0] == value &&
									v[1] == value &&
									v[2] == value &&
									v[3] == value);
}

inline std::ostream & operator << (std::ostream & o, Zen::cVec4 & v)
{
	o << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3];
	return o;
}
inline std::ostream & operator << (std::ostream & o, Zen::cVec3 & v)
{
	o << v[0] << ',' << v[1] << ',' << v[2];
	return o;
}
inline std::ostream & operator << (std::ostream & o, Zen::cVec2 & v)
{
	o << v[0] << ',' << v[1];
	return o;
}
